from flask import Flask, request, jsonify, make_response
import os
from flask_cors import CORS
from docx import Document
from pdfminer.high_level import extract_text as extract_pdf_text

from werkzeug.utils import secure_filename
import requests
import re
import json

app = Flask(__name__)
CORS(app)

UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

g_result = {}

# 大模型生成题库提示词
prompt_text = '''
# 角色

你是一位专业的题库专家，擅长根据以上材料按照要求生成对应题型的试题。你的专业知识和丰富经验使你能够准确地理解和转化用户的需求，生成满足要求的题目和答案。

## 技能

### 技能1: 生成单选题

- 根据用户提供的内容，生成单选题。

- 保证每个单选题至少有四个选项。

- 选项编号按照ABCD排列

- 输出格式：{"type":"固定值：singleChoice","question": "题目描述","options": [{"optionName":"选项编号","optionContent":"选项描述"},{"optionName":"选项编号","optionContent":"选项描述"}],"correctAnswer": "正确选项编号"}

### 技能2: 生成多选题

- 根据用户提供的内容，生成多选题。

- 保证每个多选题至少有四个选项。

- 选项编号按照ABCD排列

- 输出格式：{"type":"固定值：multipleChoice","question": "题目描述","options": [{"optionName":"选项编号","optionContent":"选项描述"},{"optionName":"选项编号","optionContent":"选项描述"}],"correctAnswer": ["正确选项编号1", "正确选项编号2"]}

### 技能3: 生成判断题

- 根据用户提供的内容，生成判断题。

- 输出格式：{"type":"固定值：trueOrFalse","question": "题目描述","correctAnswer": "Y or N"}

### 技能4: 生成简答题

- 根据用户提供的内容，生成简答题。

- 输出格式：{"type":"固定值：shortAnswer","question": "题目描述","correctAnswer": "正确答案"}

## 约束：

- 只生成与用户提供内容相关的题目。

- 必须遵守指定的输出格式，并以JSON数组的格式整合返回结果，不要包含JSON前缀和题目编号。

- 保证每个题目的描述在100字以内。

- 保证每个选项的描述在50字以内。

- 保证每个题目的正确答案在50字以内。

'''
# 题目列表
single_choice_list = []
multiple_choice_list = []
true_or_false_list = []
short_answer_list = []

# 允许的文件扩展名
ALLOWED_EXTENSIONS = {'txt', 'docx', 'pdf'}


def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def read_file_content(filepath, ext):
    if ext == 'txt':
        with open(filepath, 'r', encoding='utf-8') as f:
            return f.read()
    elif ext == 'docx':
        doc = Document(filepath)
        return '\n'.join([para.text for para in doc.paragraphs])
    elif ext == 'pdf':
        return extract_pdf_text(filepath)
    else:
        return ''

# 替代 secure_filename，保留中文但去掉特殊符号
def custom_filename(filename):
    # 去除斜杠，反斜杠等危险字符
    return filename.replace('/', '_').replace('\\', '_').replace(' ', '_')


'''
获取文件列表api
'''
@app.route('/files', methods=['GET'])
def get_files():
    files = os.listdir(UPLOAD_FOLDER)
    return jsonify(files)

'''
上传文件api
'''
@app.route('/upload', methods=['POST', 'OPTIONS'])
def upload_file():
    # 声明全局变量
    global g_result
    if request.method == 'OPTIONS':
        return jsonify({'msg': 'ok'}), 200

    if 'files' not in request.files:
        return jsonify({"error": "未找到文件"}), 400

    file = request.files['files']
    raw_filename = file.filename
    print("原始文件名：", raw_filename)

    # 允许中文：只替换非法符号
    filename = custom_filename(raw_filename)
    ext = filename.rsplit('.', 1)[-1].lower()

    if not allowed_file(filename):
        return jsonify({"error": "不支持的文件类型"}), 400

    filepath = os.path.join(UPLOAD_FOLDER, filename)
    try:
        file.save(filepath)
    except Exception as e:
        return jsonify({"error": f"保存失败：{str(e)}"}), 500

    try:
        content = read_file_content(filepath, ext)
    except Exception as e:
        return jsonify({"error": f"文件读取失败：{str(e)}"}), 500

    # result = f"根据上传的文件预览内容：\n\n{content[:100]}...\n\n文件包含 {len(content)} 个字符。"
    
    result = {
        "content": content,
        "filename": filename,
        "length": len(content)
    }
    g_result = result  # 保存到全局变量
    return result


mock_questions =  [
    {
        "type": "singleChoice",
        "question": "该方案中的请求解析模块包括操作解析单元与数据解析单元。请选出这两个子模块的主要功能。",
        "options": [
            {"optionName": "操作解析单元负责解析用户请求中的操作类型和需要的操作的数据信息。"},
            {"optionName": "数据解析单元负责解析用户的请求并提取关键的信息，以进行后续处理。"},
            {"optionName": "操作解析单元负责将操作符与用户请求中的数据进行解析，并生成类型语句。"},
            {"optionName": "数据解析单元负责从用户请求中提取关键的数据信息，并将其存储或转发给查找模块。"}
        ],
        "correctAnswer": "B"
    },
    {
        "type": "multipleChoice",
        "question": "该方案中的信息加密模块包括属性拆分单元与属性加密单元。请选出这两个子模块的主要功能。",
        "options": [
            {"optionName": "属性拆分单元负责将数据按照某个属性进行分割和处理，以便于后续的加密和解密操作。"},
            {"optionName": "属性拆分单元负责将用户请求中的操作符与数据按属性进行拆分，并生成类型语句。"},
            {"optionName": "属性拆分单元负责将数据按照某个属性进行分割和处理，便于后续处理的操作。"},
            {"optionName": "属性拆分单元负责将用户请求中的操作符与数据按属性进行处理，并生成类型语句。"}
        ],
        "correctAnswer": ["B", "C"]
    },
    {
        "type": "multipleChoice",
        "question": "该方案中的信息加密模块包括属性拆分单元与属性加密单元。请选出这两个子模块的主要功能。",
        "options": [
            {"optionName": "属性拆分单元负责将数据按照某个属性进行分割和处理，以便于后续的加密和解密操作。"},
            {"optionName": "属性拆分单元负责将用户请求中的操作符与数据按属性进行拆分，并生成类型语句。"},
            {"optionName": "属性拆分单元负责将数据按照某个属性进行分割和处理，便于后续处理的操作。"},
            {"optionName": "属性拆分单元负责将用户请求中的操作符与数据按属性进行处理，并生成类型语句。"}
        ],
        "correctAnswer": ["B", "C"]
    },
    {
        "type": "multipleChoice",
        "question": "该方案中的信息加密模块包括属性拆分单元与属性加密单元。请选出这两个子模块的主要功能。",
        "options": [
            {"optionName": "属性拆分单元负责将数据按照某个属性进行分割和处理，以便于后续的加密和解密操作。"},
            {"optionName": "属性拆分单元负责将用户请求中的操作符与数据按属性进行拆分，并生成类型语句。"},
            {"optionName": "属性拆分单元负责将数据按照某个属性进行分割和处理，便于后续处理的操作。"},
            {"optionName": "属性拆分单元负责将用户请求中的操作符与数据按属性进行处理，并生成类型语句。"}
        ],
        "correctAnswer": ["B", "C"]
    },
    {
        "type": "multipleChoice",
        "question": "该方案中的信息加密模块包括属性拆分单元与属性加密单元。请选出这两个子模块的主要功能。",
        "options": [
            {"optionName": "属性拆分单元负责将数据按照某个属性进行分割和处理，以便于后续的加密和解密操作。"},
            {"optionName": "属性拆分单元负责将用户请求中的操作符与数据按属性进行拆分，并生成类型语句。"},
            {"optionName": "属性拆分单元负责将数据按照某个属性进行分割和处理，便于后续处理的操作。"},
            {"optionName": "属性拆分单元负责将用户请求中的操作符与数据按属性进行处理，并生成类型语句。"}
        ],
        "correctAnswer": ["B", "C"]
    },
    {
        "type": "multipleChoice",
        "question": "该方案中的信息加密模块包括属性拆分单元与属性加密单元。请选出这两个子模块的主要功能。",
        "options": [
            {"optionName": "属性拆分单元负责将数据按照某个属性进行分割和处理，以便于后续的加密和解密操作。"},
            {"optionName": "属性拆分单元负责将用户请求中的操作符与数据按属性进行拆分，并生成类型语句。"},
            {"optionName": "属性拆分单元负责将数据按照某个属性进行分割和处理，便于后续处理的操作。"},
            {"optionName": "属性拆分单元负责将用户请求中的操作符与数据按属性进行处理，并生成类型语句。"}
        ],
        "correctAnswer": ["B", "C"]
    },
    {
        "type": "trueOrFalse",
        "question": "该方案在云服务器安全环境中实现机密计算，确保数据完整性和安全性。",
        "options": [
            {"optionName": "正确。该方案通过将信息存储于非安全区，以确保数据的完整性。"},
            {"optionName": "该方案需要在云服务器上进行加密处理，以保护用户的数据隐私和安全性。"}
        ],
        "correctAnswer": "Y"
    },
    {
        "type": "shortAnswer",
        "question": "查找模块的功能包括单属性查找和范围查找。",
        "correctAnswer": ["单属性查找单元", "范围查找单元"]
    },
    {
        "type": "trueOrFalse",
        "question": "该方案在云服务器安全环境中实现机密计算，确保数据完整性和安全性。",
        "options": [
            {"optionName": "正确。该方案通过将信息存储于非安全区，以确保数据的完整性。"},
            {"optionName": "该方案需要在云服务器上进行加密处理，以保护用户的数据隐私和安全性。"}
        ],
        "correctAnswer": "Y"
    },
    {
        "type": "trueOrFalse",
        "question": "该方案在云服务器安全环境中实现机密计算，确保数据完整性和安全性。",
        "options": [
            {"optionName": "正确。该方案通过将信息存储于非安全区，以确保数据的完整性。"},
            {"optionName": "该方案需要在云服务器上进行加密处理，以保护用户的数据隐私和安全性。"}
        ],
        "correctAnswer": "Y"
    },
    {
        "type": "trueOrFalse",
        "question": "该方案在云服务器安全环境中实现机密计算，确保数据完整性和安全性。",
        "options": [
            {"optionName": "正确。该方案通过将信息存储于非安全区，以确保数据的完整性。"},
            {"optionName": "该方案需要在云服务器上进行加密处理，以保护用户的数据隐私和安全性。"}
        ],
        "correctAnswer": "Y"
    },
]



class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, set):
            return list(obj)
        return super().default(obj)

'''
调用大模型生成题库api
'''
@app.route('/generate', methods=['POST'])
def generate_content():
    # 从全局变量获取上传文件内容
    # data = g_result
    # file_content = data.get('content', '')  # 获取文件内容
    
    print(request.json['selected_files'])
    # if not file_content:
        # return jsonify({"error": "未提供文件内容"}), 400

    # 调用本地Ollama大模型 API，采用生成方式调用模型
    model_api_url = 'http://127.0.0.1:11434/api/generate'  # 修改为您的大模型API地址
    # api_key = 'your_openai_api_key_here'  # 替换为您的 API 密钥

    # 请求体格式，根据大模型 API 要求调整
    # payload = {
    #     "model": "deepseek-r1:1.5b",  # 使用的模型名称
    #     "prompt": file_content[0:1024] + "," + prompt_text,  # 文件内容作为提示
    #     # "max_tokens": 150  # 最大生成长度
    #     "stream": False,  # 流式输出
    # }

    headers = {
        # "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }

    try:
        # 发送请求到大模型 API
        # response = requests.post(model_api_url, json=payload, headers=headers)
        # print(response.text)
        # response.raise_for_status()  # 如果返回状态码不是 2xx，会抛出异常
        # result = response.json()
        # print(result)

        # 假设返回的结果包含生成的文本
        # generated_text = result.get("response", "")
        # print(generated_text)
        # # print(generated_text)
        # match = re.search(
        #     r'```\s*json\s*\n(.*?)```',  # 匹配 json 代码块内容
        #     generated_text,
        #     flags = re.DOTALL  # 支持跨行匹配
        # )

        # if match:
        #     json_content = match.group(1).strip()  # 提取内容并去除首尾空行
        # else:
        #     json_content = json.loads(generated_text)
        
        # print(json_content)
        # json_content = json.dumps(mock_questions)
        # print(json_content)
        response = make_response(json.dumps({"questions":mock_questions}, cls=CustomEncoder))
        # response.headers['Content-Type'] = 'application/json'
        return response
        # return jsonify({"questions": mock_questions})

    except requests.exceptions.RequestException as e:
        return jsonify({"error": f"调用大模型 API 失败：{str(e)}"}), 500


'''
导出题库api
'''
@app.route('/export', methods=['POST'])
def export_questions():
    # 从全局变量获取上传文件内容
    data = g_result
    file_content = data.get('content', '')  # 获取文件内容


'''
编制试卷
'''
@app.route('/arrange', methods=['POST'])
def arrange_questions():
    # 从全局变量获取上传文件内容
    data = g_result
    file_content = data.get('content', '')  # 获取文件内容

if __name__ == '__main__':
    app.run(debug=True)